package com.vben.auth.service;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.vben.auth.constant.LoginConstant;
import com.vben.common.core.constant.CacheKeyConstant;
import com.vben.common.core.constant.CommonConstant;
import com.vben.common.core.util.R;
import com.vben.common.exception.auth.LoginException;
import com.vben.common.exception.auth.LoginParamException;
import com.vben.common.exception.auth.UserNotFoundException;
import com.vben.common.exception.enums.ErrorCode;
import com.vben.common.redis.service.RedisService;
import com.vben.system.api.dto.user.SysUserLoginDTO;
import com.vben.system.api.entity.SysUser;
import com.vben.system.api.entity.SysUserLoginLog;
import com.vben.system.api.feign.RemoteUserService;
import com.vben.system.api.vo.user.SysUserLoginVO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 登陆服务
 *
 * @author sunnfun
 */
@Service
@Slf4j
public class LoginService {

    @Resource
    private RemoteUserService remoteUserService;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private RedisService redisService;

    /**
     * 用户账号密码登陆
     *
     * @param loginInfo 登陆信息
     * @return 登陆返回信息
     */
    public SysUserLoginVO login(SysUserLoginDTO loginInfo, HttpServletRequest request) {
        SysUser sysUser = new SysUser();
        String loginId = IdUtil.getSnowflakeNextIdStr();
        String loginStatus = LoginConstant.LOGIN_STATUS_FAIL;
        try {
            String username = loginInfo.getUsername();
            String password = loginInfo.getPassword();

            if (StrUtil.isEmptyIfStr(username) || StrUtil.isEmptyIfStr(password)) {
                throw new LoginParamException("用户名或密码为空");
            }

            // 查询用户
            R<SysUser> remoteResult = remoteUserService.getUserByUsername(username);
            if (!R.isOk(remoteResult)) {
                throw new LoginException(ErrorCode.INTERNAL_ERROR, "系统服务远程调用失败");
            }

            sysUser = remoteResult.getData();
            if (BeanUtil.isEmpty(sysUser)) {
                throw new UserNotFoundException();
            }
            checkUserLock(sysUser);
            // 校验密码
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            boolean verified = encoder.matches(password, sysUser.getPassword());
            if (!verified) {
                throw new LoginException(ErrorCode.PASSWORD_MISMATCH);
            }

            // sa-token 用户登陆
            StpUtil.login(sysUser.getUserId(), loginId);

            //封装token
            String token = StpUtil.getTokenValue();
            log.info("用户使用账号密码登陆成功,账号:{}", username);
            loginStatus = LoginConstant.LOGIN_STATUS_SUCCESS;
            updateLastLoginTime(sysUser);
            saveUserAuthInfoToRedis(sysUser.getUserId());
            return new SysUserLoginVO(token);
        } finally {
            //记录登陆日志
            loginLogService.addLoginLog(sysUser, loginId, loginStatus, request);
        }
    }

    /**
     * 手机验证码登陆(TODO 接入短信服务，实现真实的验证码登陆)
     *
     * @param loginInfo 手机号和验证码
     * @return 登陆返回信息
     */
    public SysUserLoginVO loginByPhone(SysUserLoginDTO loginInfo, HttpServletRequest request) {
        SysUser sysUser = new SysUser();
        String longinId = IdUtil.getSnowflakeNextIdStr();
        String loginStatus = LoginConstant.LOGIN_STATUS_FAIL;
        try {
            String phone = loginInfo.getPhone();
            String code = loginInfo.getCode();
            if (StrUtil.isBlankIfStr(phone) || StrUtil.isBlankIfStr(code)) {
                throw new LoginException(ErrorCode.PARAM_INVALID, "手机号或验证码为空");
            }

            // 检查手机号是否存在，若不存在，创建用户
            R<SysUser> remoteResult = remoteUserService.getUserByPhone(phone);
            if (!R.isOk(remoteResult)) {
                throw new LoginException(ErrorCode.INTERNAL_ERROR, "系统服务调用失败");
            }

            sysUser = remoteResult.getData();
            if (BeanUtil.isEmpty(sysUser)) {
                SysUser newUser = new SysUser();
                newUser.setUsername(phone);
                newUser.setPhone(phone);
                newUser.setNickname(phone);
                //二次元永不为奴!!!!
                newUser.setAvatar("https://hututu-prod-1320667610.cos.ap-beijing.myqcloud.com/public/1881752164258402305/2025-01-22_AkXtG9ABoHFIuJyH.png");
                //设置初始密码，默认为手机号
                newUser.setPassword(phone);
                R<Long> addResult = remoteUserService.addUser(newUser);
                if (!R.isOk(addResult)) {
                    log.error("远程新增用户失败:{}", addResult.getMessage());
                    throw new LoginException(ErrorCode.PARAM_INVALID, "新增用户失败");
                }
                log.info("手机号:{}不存在，创建新用户成功", phone);
                newUser.setUserId(addResult.getData());
                sysUser = newUser;
            }
            checkUserLock(sysUser);
            // 封装token
            StpUtil.login(sysUser.getUserId(), longinId);
            String token = StpUtil.getTokenValue();

            log.info("用户使用手机验证码登陆成功,手机号:{},验证码:{}", phone, code);
            updateLastLoginTime(sysUser);
            saveUserAuthInfoToRedis(sysUser.getUserId());
            loginStatus = LoginConstant.LOGIN_STATUS_SUCCESS;
            return new SysUserLoginVO(token);
        } finally {
            loginLogService.addLoginLog(sysUser, longinId, loginStatus, request);
        }
    }

    /**
     * 注销登陆
     */
    public void logout() {
        try {
            // 获取loginId
            String loginId = StpUtil.getLoginDevice();
            SysUserLoginLog loginLog = loginLogService.getByLoginId(loginId);
            if (BeanUtil.isEmpty(loginLog)) {
                log.error("登陆id:{}对应的登陆日志不存在，更新登陆日志失败", loginId);
            }
            loginLog.setLogoutFlag(CommonConstant.COMMON_YSE);
            loginLog.setLogoutTime(LocalDateTime.now());
            loginLogService.updateLoginLog(loginLog);
        } catch (Exception e) {
            log.error("用户注销更新登陆日志失败:{}", e.getMessage());
        } finally {
            StpUtil.logout();
        }
    }

    /**
     * 更新用户上次登陆时间
     */
    private void updateLastLoginTime(SysUser user) {
        if (BeanUtil.isEmpty(user)) {
            throw new LoginException(ErrorCode.PARAM_INVALID, "用户信息为空");
        }
        user.setLastLoginTime(LocalDateTime.now());
        R<Boolean> updateResult = remoteUserService.updateUser(user);
        if (!R.isOk(updateResult)) {
            log.error("更新用户最后登陆时间失败:{}", updateResult.getMessage());
        }
    }

    /**
     * 保存用户角色和权限信息到redis
     *
     * @param userId 用户ID
     */
    private void saveUserAuthInfoToRedis(Long userId) {
        R<List<String>> userRoleList = remoteUserService.getUserRoleList(userId);
        R<List<String>> userPermissionList = remoteUserService.getUserPermissionList(userId);
        if (R.isOk(userRoleList)) {
            List<String> roleList = userRoleList.getData();
            if (CollUtil.isNotEmpty(roleList)) {
                Set<String> roleSet = new HashSet<>(roleList);
                redisService.addSet(CacheKeyConstant.USER_ROLE_LIST + userId, roleSet);
            }
        }
        if (R.isOk(userPermissionList)) {
            if (CollUtil.isNotEmpty(userPermissionList.getData())) {
                HashSet<String> permissionSet = new HashSet<>(userPermissionList.getData());
                redisService.addSet(CacheKeyConstant.USER_PERMISSION_LIST + userId, permissionSet);
            }
        }
    }

    /**
     * 检查用户是否锁定
     */
    private void checkUserLock(SysUser user) {
        if (BeanUtil.isEmpty(user)) {
            throw new LoginException(ErrorCode.PARAM_INVALID, "用户信息为空");
        }
        if (CommonConstant.STATUS_LOCK.equals(user.getLockFlag())) {
            throw new LoginException(ErrorCode.ACCOUNT_LOCKED);
        }
    }
}
